package com.pobox.tupletest;

import org.apache.log4j.Logger;

import java.util.ArrayList;
import java.util.List;

public class Table extends ArrayList<Dimension> {
    /**
     * Logger for this class
     */
    private static final Logger logger = Logger.getLogger(Table.class);

    private static final long serialVersionUID = 1L;
    private List<Tuple> wanted;
    private List<Tuple> coveredTuples = new ArrayList<Tuple>();
    private int itemComboPerTuple;
    private static final char[] featureName = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
            .toCharArray();

    public Table() {
        super();
    }

    /**
     * Convenience Constructor - pass in an array of ints - with array length ==
     * number of dimensions desired, and each int in the array describing how
     * many features are in that dimension. For example pass in
     * <code>new int[]{4,3,5}</code> and you get back a table populated with
     * features/items like this:
     * <code>
     * 1a 2a 3a
     * 1b 2b 3b
     * 1c 2c 3c
     * 1d    3d
     *       3e
     * </code>
     * @param an array of ints of length equal number of dimensions, and each
     *            index int describing the number of features in that dimension.
     */
    public Table(int[] ints) {
        super();
        createTableFromIntArray(ints);
    }

   void createTableFromIntArray(int[] ints) {
        int numOfDimensionsRequired = ints.length;
        assert numOfDimensionsRequired > 1 : "need a minimum of 2 dimensions";

        for (int dim = 0; dim < numOfDimensionsRequired; dim++) {
            Dimension dimension = new Dimension();
            int numFeaturesRequired = ints[dim];
            assert numFeaturesRequired > 0 : "need a minimum of 1 feature for dimension "+dim;
            for (int feature = 0; feature < numFeaturesRequired; feature++) {
                Item item = new Item(dim + 1, featureName[feature]);
                dimension.add(item);
            }
            add(dimension);
        }
    }

    public int dimensionCount() {
        logger.debug("dimensionCount() - start");

        int returnint = size();
        logger.debug("dimensionCount() - end");
        return returnint;
    }

    public List getAllItems() {
        logger.debug("getAllItems() - start");

        List<Item> everyItem = new ArrayList<Item>();
        for (Dimension dimension : this) {
            everyItem.addAll(dimension);
        }

        logger.debug("getAllItems() - end");
        return everyItem;
    }

    public List<Tuple> getAllWantedTuples(int itemComboPerTuple) {
        logger.debug("getAllWantedTuples(int) - start");

        // simple attempt at caching the wanted tuples, as long as the
        // strength asked for (num ietms in tuple pairs, triples of features etc
        // is the same as that previously generated.
        if (this.itemComboPerTuple != itemComboPerTuple) {
            this.itemComboPerTuple = itemComboPerTuple;
            wanted = new ArrayList<Tuple>();
            buildTuples(wanted, 0, new Tuple(), itemComboPerTuple);
        }

        logger.debug("getAllWantedTuples(int) - end");
        return wanted;
    }

    public int getNumberOfItems() {
        logger.debug("getNumberOfItems() - start");

        int count = 0;
        for (Dimension dimension : this) {
            count = count + dimension.size();
        }

        logger.debug("getNumberOfItems() - end");
        return count;
    }

    public int getTheoreticalMinimumTestCount(int itemsInTuple) {
        logger.debug("getTheoreticalMinimumTestCount(int) - start");

        // product of the n-largest dimensions is minimum test cases number
        int[] largestDimensions = discoverLargestNdimensions(itemsInTuple);
        int returnint = computeProduct(largestDimensions);
        logger.debug("getTheoreticalMinimumTestCount(int) - end");
        return returnint;
    }

    private void buildTuples(List<Tuple> wanted, int startDimension,
            Tuple tuple, int itemsPerTuple) {
        logger.debug("buildTuples(List<Tuple>, int, Tuple, int) - start");

        dimensionLoop: for (int j = startDimension; j < size(); j++) {
            Dimension dimension2 = get(j);
            itemsLoop: for (Item item : dimension2) {
                Tuple holdTuple = new Tuple(tuple);
                tuple.add(item);
                if (needsMoreFeaturesInTuple(tuple, itemsPerTuple)) {
                    buildTuples(wanted, j + 1, tuple, itemsPerTuple);
                } else { // TODO: hillclimbing - here?
                    wanted.add(tuple);
                }
                tuple = holdTuple;
            }
        }

        logger.debug("buildTuples(List<Tuple>, int, Tuple, int) - end");
    }

    private boolean needsMoreFeaturesInTuple(Tuple tuple, int itemsPerTuple) {
        logger.debug("needsMoreFeaturesInTuple(Tuple, int) - start");

        boolean returnboolean = tuple.size() < itemsPerTuple;
        logger.debug("needsMoreFeaturesInTuple(Tuple, int) - end");
        return returnboolean;
    }

    private int computeProduct(int[] largestDimensions) {
        logger.debug("computeProduct(int[]) - start");

        int minimumNumberOfTests = 1;
        for (int element : largestDimensions) {
            minimumNumberOfTests *= element;
        }

        logger.debug("computeProduct(int[]) - end");
        return minimumNumberOfTests;
    }

    private int[] discoverLargestNdimensions(int itemsInTuple) {
        logger.debug("discoverLargestNdimensions(int) - start");

        int[] largestDimensions = new int[itemsInTuple];
        for (Dimension dimension : this) {
            arrayLoop: for (int i = 0; i < largestDimensions.length; i++) {
                if (largestDimensions[i] < dimension.itemCount()) {
                    largestDimensions[i] = dimension.itemCount();
                    break arrayLoop;
                }
            }
        }

        logger.debug("discoverLargestNdimensions(int) - end");
        return largestDimensions;
    }

    public List<Tuple> getCoveredTuples() {
        logger.debug("getCoveredTuples() - start");

        return coveredTuples;
    }

    public int getItemComboPerTuple() {
        logger.debug("getItemComboPerTuple() - start");

        return itemComboPerTuple;
    }

}
